summaryrefslogtreecommitdiffstatshomepage
path: root/frontend/src/routes/comics/[id]/+page.svelte
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/routes/comics/[id]/+page.svelte')
-rw-r--r--frontend/src/routes/comics/[id]/+page.svelte176
1 files changed, 176 insertions, 0 deletions
diff --git a/frontend/src/routes/comics/[id]/+page.svelte b/frontend/src/routes/comics/[id]/+page.svelte
new file mode 100644
index 0000000..cfc5840
--- /dev/null
+++ b/frontend/src/routes/comics/[id]/+page.svelte
@@ -0,0 +1,176 @@
+<script lang="ts">
+ import { beforeNavigate } from '$app/navigation';
+ import { updateComics } from '$gql/Mutations';
+ import { comicQuery } from '$gql/Queries';
+ import { comicEquals } from '$gql/Utils';
+ import { UpdateMode, type FullComicFragment, type UpdateComicInput } from '$gql/graphql';
+ import { initReaderContext } from '$lib/Reader';
+ import { initScraperContext } from '$lib/Scraper';
+ import { initSelectionContext } from '$lib/Selection';
+ import { setTabContext } from '$lib/Tabs';
+ import { toastFinally } from '$lib/Toasts';
+ import { preventOnPending } from '$lib/Utils';
+ import BookmarkButton from '$lib/components/BookmarkButton.svelte';
+ import Guard from '$lib/components/Guard.svelte';
+ import Head from '$lib/components/Head.svelte';
+ import OrganizedButton from '$lib/components/OrganizedButton.svelte';
+ import RemovePageButton from '$lib/components/RemovePageButton.svelte';
+ import SubmitButton from '$lib/components/SubmitButton.svelte';
+ import Titlebar from '$lib/components/Titlebar.svelte';
+ import Grid from '$lib/containers/Grid.svelte';
+ import ComicForm from '$lib/forms/ComicForm.svelte';
+ import Gallery from '$lib/gallery/Gallery.svelte';
+ import PageView from '$lib/reader/PageView.svelte';
+ import Reader from '$lib/reader/Reader.svelte';
+ import ComicScrapeForm from '$lib/scraper/ComicScrapeForm.svelte';
+ import ComicDelete from '$lib/tabs/ComicDelete.svelte';
+ import ComicDetails from '$lib/tabs/ComicDetails.svelte';
+ import Tab from '$lib/tabs/Tab.svelte';
+ import Tabs from '$lib/tabs/Tabs.svelte';
+ import SelectionControls from '$lib/toolbar/SelectionControls.svelte';
+ import { getContextClient } from '@urql/svelte';
+ import type { PageData } from './$types';
+
+ const client = getContextClient();
+ const reader = initReaderContext();
+ const selection = initSelectionContext();
+ const scraper = initScraperContext();
+ const tabContext = setTabContext({
+ tabs: {
+ details: { title: 'Details' },
+ edit: { title: 'Edit' },
+ scrape: { title: 'Scrape' },
+ deletion: { title: 'Delete' }
+ },
+ current: 'details'
+ });
+
+ export let data: PageData;
+ $: result = comicQuery(client, { id: data.id });
+
+ let comic: FullComicFragment;
+ let original: Readonly<FullComicFragment>;
+ let updatePartial = false;
+
+ $: $result, update();
+ $: pending = !comicEquals(comic, original);
+ $: $tabContext.tabs.edit.badge = pending;
+
+ function update() {
+ if (!$result.stale && $result.data?.comic.__typename === 'FullComic') {
+ original = $result.data.comic;
+ if (updatePartial) {
+ comic.pages = structuredClone(original.pages);
+ comic.favourite = original.favourite;
+ comic.bookmarked = original.bookmarked;
+ comic.organized = original.organized;
+ comic.updatedAt = original.updatedAt;
+ updatePartial = false;
+ } else {
+ comic = structuredClone(original);
+ }
+
+ $reader.pages = original.pages;
+ $selection.view = comic.pages;
+ $scraper.selector = undefined;
+ }
+ }
+
+ function toggle(field: keyof Omit<UpdateComicInput, 'cover'>) {
+ updateComics(client, { ids: comic.id, input: { [field]: !comic[field] } })
+ .then(() => (updatePartial = true))
+ .catch(toastFinally);
+ }
+
+ function updateComic(event: CustomEvent<UpdateComicInput>) {
+ updateComics(client, { ids: comic.id, input: event.detail }).catch(toastFinally);
+ }
+
+ function updateCover(event: CustomEvent<number>) {
+ updateComics(client, { ids: comic.id, input: { cover: { id: event.detail } } })
+ .then(() => (updatePartial = true))
+ .catch(toastFinally);
+ }
+
+ function removePages() {
+ updateComics(client, {
+ ids: comic.id,
+ input: { pages: { ids: $selection.ids, options: { mode: UpdateMode.Remove } } }
+ })
+ .then(() => {
+ updatePartial = true;
+ $selection = $selection.clear();
+ })
+ .catch(toastFinally);
+ }
+
+ beforeNavigate((navigation) => preventOnPending(navigation, pending));
+</script>
+
+<Head section="Comic" title={original?.title} />
+
+{#if comic}
+ <Grid>
+ <header>
+ <Titlebar
+ title={original.title}
+ subtitle={original.originalTitle}
+ bind:favourite={comic.favourite}
+ on:favourite={() => toggle('favourite')}
+ />
+ </header>
+
+ <aside>
+ <Tabs>
+ <Tab id="details">
+ <ComicDetails comic={original} />
+ </Tab>
+ <Tab id="edit">
+ <div class="flex flex-col gap-4">
+ <div class="flex gap-2 text-sm">
+ <SelectionControls page>
+ <RemovePageButton on:click={removePages} />
+ </SelectionControls>
+ <div class="grow" />
+ <BookmarkButton bookmarked={comic.bookmarked} on:click={() => toggle('bookmarked')} />
+ <OrganizedButton organized={comic.organized} on:click={() => toggle('organized')} />
+ </div>
+ <ComicForm bind:comic on:submit={updateComic}>
+ <div class="flex gap-2">
+ <div class="grow" />
+ <SubmitButton active={pending} />
+ </div>
+ </ComicForm>
+ </div>
+ </Tab>
+ <Tab id="scrape">
+ <ComicScrapeForm {comic} />
+ </Tab>
+ <Tab id="deletion">
+ <ComicDelete {comic} />
+ </Tab>
+ </Tabs>
+ </aside>
+
+ <main class="overflow-auto">
+ <Gallery
+ pages={comic.pages}
+ on:open={(e) => ($reader = $reader.open(e.detail))}
+ on:cover={updateCover}
+ />
+ </main>
+ </Grid>
+
+ <Reader>
+ <PageView layout={comic.layout} direction={comic.direction} />
+ <svelte:fragment slot="sidebar">
+ <ComicForm bind:comic on:submit={updateComic}>
+ <div class="flex justify-end gap-2">
+ <SubmitButton active={pending} />
+ </div>
+ </ComicForm>
+ </svelte:fragment>
+ </Reader>
+{:else}
+ <Guard {result} />
+{/if}